package dovs.display;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.awt.Dialog.ModalityType;
import javax.swing.*;
import javax.swing.tree.*;
import dovs.analysis.*;
import dovs.node.*;

/**
 *	ASTDisplay traverses the AST while constructing tree data and displays the
 *	tree on {@link #outAProgram(AProgram)}. Use this as the base class for
 *	customized AST displays of the individual phases.
 */
public class ASTDisplay extends DepthFirstAdapter {
	/**
	 * The dialog title
	 */
	private String title;
	
	/**
	 * The parents stack used to construct the tree data during traversal.
	 */
    private Stack<DefaultMutableTreeNode> parents 
    	= new Stack<DefaultMutableTreeNode>();
 
    /**
     * Creates a new ASTDisplay with the given dialog title.
     * @param title
     * 		the displayed dialog title
     */
    public ASTDisplay(String title) {
    	this.title = title;
    }
    
    /**
     * Adds a field to the node on the top of parents stack.
     * @param label
     * 		the field label
     * @param value
     * 		the displayed value (may be null) 
     */
    protected void addField(String label, Object value) {
    	if (!parents.isEmpty()) {
    		String valueText = value != null ? value.toString() : "null";
    		parents.peek().add(new DefaultMutableTreeNode(
    				label + "=" + valueText));
    	}
    }
    
    /**
     * Adds a map to the node on the top of parents stack.
     * @param label
     * 		the map label
     * @param map
     * 		the displayed map (must be nonnull) 
     */
    protected void addMap(String label, Map<?,?> map) {
    	if (!parents.isEmpty()) {
    		DefaultMutableTreeNode mapNode = new DefaultMutableTreeNode(label);
    		for (Map.Entry<?,?> entry : map.entrySet()) {
        		String keyText = entry.getKey() != null ? entry.getKey().toString() : "null";
        		String valueText = entry.getValue() != null ? entry.getValue().toString() : "null";
        		mapNode.add(new DefaultMutableTreeNode(keyText + "=" + valueText));
    		}
    		parents.peek().add(mapNode);
    	}
    }
 
    @Override @SuppressWarnings("unused")
	public void outAProgram(AProgram node) {
    	createAndShowDisplay();
    }

	/**
	 * As we come across nonterminals, push them onto the stack
	 * @param node
	 * 		the handled node
	 */
    @Override
	public void defaultIn(Node node) {
    	String label = node.getClass().getSimpleName();
		DefaultMutableTreeNode thisNode 
			= new DefaultMutableTreeNode(label);
		parents.push(thisNode);
    }

	/**
	 * As we leave a non terminal, it's parent is the node before it
	 * on the stack, so we pop it off and add it to that node
	 */
    @Override @SuppressWarnings("unused")
	public void defaultOut(Node node) {
		DefaultMutableTreeNode thisNode = parents.pop();
		parents.peek().add(thisNode);
    }

	/**
	 *	Terminals - our parent is always on the top of the stack, so we
	 *	add ourselves to it
	 *	@param token
	 *		the added token
	 */
    @Override
	public void defaultToken(Token token) {
    	String label = token.getClass().getSimpleName()
    		+ "(\"" + token.getText() + "\")";
		DefaultMutableTreeNode thisNode 
			= new DefaultMutableTreeNode(label);
		parents.peek().add(thisNode);
    }
    
    @Override @SuppressWarnings("unused")
    public final void caseStart(Start node) {
    	// avoid traversal since Start nodes are not part of the program
    }    
    @Override @SuppressWarnings("unused")
	public final void caseEOF(EOF node) {
    	// avoid traversal since EOF tokens are not part of the program
    }
    
    /**
     * Creates and shows a display with the collected tree data.
     */
    private final void createAndShowDisplay() {
    	// Create a modal dialog with the given title
    	// Use DOCUMENT_MODAL to allow multiple modal dialogs
        JDialog display = new JDialog(null, title, ModalityType.DOCUMENT_MODAL);
        // Create a tree view of the collected tree data
		JTree tree = new JTree(parents.pop());
		JScrollPane pane = new JScrollPane(tree);
		// Expand all nodes in the tree view
		expandAll(tree, true);

		display.setSize(400, 500);
		display.getContentPane().add(pane);
		// show the display
		showDisplay(display);
    }
    
    ////////////////////////////////////////////////////////////////////////////
    //	static implementation
    ////////////////////////////////////////////////////////////////////////////
	
    /**
     * Enum used for designating for which phases the AST should be displayed.
     */
	public enum Phase {
		PARSER("-Parser"), 
		WEEDING("-Weeding"),
		ENVIRONMENTS("-Environments"),
		TYPE_LINKING("-TypeLinking"),
		HIERARCHY("-Hierarchy"),
		DISAMBIGUATION("-Disambiguation"),
		TYPE_CHECKING("-TypeChecking"),
		CONSTANT_FOLDING("-ConstantFolding"),
		REACHABILITY("-Reachability"),
		DEFINITE_ASSIGNMENT("-DefiniteAssignment"),
		RESOURCES("-Resources"),
		CODE_GENERATION("-CodeGeneration"),
		OPTIMIZATION("-Optimization"),
		LIMITS("-Limits"),
		CODE_EMISSION("-CodeEmission"),
		;
		
		public final String commandline_option;
		Phase(String option) {
			commandline_option = option;
		}
	}
	
    /**
     * The set of phases for which the AST is displayed.
     */
	private static EnumSet<Phase> displayPhases = EnumSet.noneOf(Phase.class);
    
    /**
     * Adds the given phase to the set of phases for which the AST is displayed.
     * @param phase
     * 		the added phase
     */
    public static void addDisplayPhase(Phase phase) {
    	displayPhases.add(phase);
    }
    
    /**
     * Clears the set of phases for which the AST is displayed.
     */
    public static void clearDisplayPhases() {
    	displayPhases.clear();
    }
    
    /**
     * Shows the ASTDisplay for the given phase. The AST is only display if
     * an implementation has been provided for the phase and its display has 
     * been enabled through its respective commandline option. 
     * @param program
     * 		the program AST
     * @param phase
     * 		the phase for which to display the AST
     */
    public static void showPhaseDisplay(AProgram program, Phase phase) {
   		if (displayPhases.contains(phase)) {
    		switch (phase) {
    		case PARSER:
    			// display the AST after Parser
				program.apply(new ParserDisplay());
				break;
    		case WEEDING:
				// display the AST after Weeding
				program.apply(new WeedingDisplay());
				break;
    		case ENVIRONMENTS:
				// TODO add code here to display the AST after Environments
    			break;
    		case TYPE_LINKING:
				// TODO add code here to display the AST after TypeLinking
    			break;
    		case HIERARCHY:
				// TODO add code here to display the AST after Hierarchy
    			break;
    		case DISAMBIGUATION:
				// TODO add code here to display the AST after Disambiguation
    			break;
    		case TYPE_CHECKING:
				// TODO add code here to display the AST after TypeChecking
    			break;
    		case CONSTANT_FOLDING:
				// TODO add code here to display the AST after ConstantFolding
    			break;
    		case REACHABILITY:
				// TODO add code here to display the AST after Reachability
    			break;
    		case DEFINITE_ASSIGNMENT:
				// TODO add code here to display the AST after DefiniteAssignment
    			break;
    		case RESOURCES:
				// TODO add code here to display the AST after Resources
    			break;
    		case CODE_GENERATION:
				// TODO add code here to display the AST after CodeGeneration
    			break;
    		case OPTIMIZATION:
				// TODO add code here to display the AST after Optimization
    			break;
    		case LIMITS:
				// TODO add code here to display the AST after Limits
    			break;
    		case CODE_EMISSION:
				// TODO add code here to display the AST after CodeEmission
    			break;
    		}
    	}
    }

    /**
     * Wait for all open displays to be closed before continuing.
     */
    public static void awaitDisplays() {
    	try {
    		executorService.shutdown();
        	// Wait for displays to be closed but at most 
    		// for the given time period (just change if needed)
    		executorService.awaitTermination(5, TimeUnit.MINUTES);
    	} catch (InterruptedException e) {
    		// Ignore (and terminate compilation)
    	}
    }


    /**
     * ExecutorService used to synchronize termination.
     */
    private static final ExecutorService executorService = Executors.newCachedThreadPool();
 
    /**
     * Shows the display, delaying compiler termination until 
     * the display is closed.
     * @param display
     * 		the shown display
     */
    protected static void showDisplay(final JDialog display) {
    	executorService.execute(new Runnable() {
    		public void run() {
    			// show dialog (and wait if dialog is modal)
   				display.setVisible(true);
   			}
   		});
	}
    
    /**
     * If expand is {@code true}, expands all nodes in the tree. Otherwise, 
     * collapses all nodes in the tree.
     * @param tree
     * 		the expanded/collapsed tree
     * @param expand
     * 		expand/collapse switch
     */
    // 
    public static void expandAll(JTree tree, boolean expand) {
        TreeNode root = (TreeNode)tree.getModel().getRoot();
    
        // Traverse tree from root
        expandAll(tree, new TreePath(root), expand);
    }

    private static void expandAll(JTree tree, TreePath parent, boolean expand) {
        // Traverse children
        TreeNode node = (TreeNode)parent.getLastPathComponent();
        if (node.getChildCount() >= 0) {
            for (Enumeration<?> e=node.children(); e.hasMoreElements(); ) {
                TreeNode n = (TreeNode)e.nextElement();
                TreePath path = parent.pathByAddingChild(n);
                expandAll(tree, path, expand);
            }
        }
    
        // Expansion or collapse must be done bottom-up
        if (expand) {
            tree.expandPath(parent);
        } else {
            tree.collapsePath(parent);
        }
    }
}
